﻿/*
   Open Intel 
   Copyright © 2011 – ISC. All Rights Reserved.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Media.Imaging;
using Caliburn.Micro;
using ISC.MapDotNetServer.Common.Maps;
using OI.Events;

namespace OI.Framework
{
    // abstract base for layer level items in the legend as well as classification level items
    public abstract class LegendItemBase : PropertyChangedBase, IEquatable<LegendItemBase>
    {
        // number of characters to display on label
        protected const int LablelTruncLength = 28;

        // event aggregator
        protected readonly IEventAggregator Events;

        // icon cache
        protected static Dictionary<string, BitmapImage> IconCache = new Dictionary<string, BitmapImage>();

        // icon provider
        // takes LayerID, ClassID, CurrentScale, icon size and returns a BitmapImage 
        protected Func<string, string, double, int, BitmapImage> IconProvider { get; private set; }

        // associated Layer
        public Layer Layer { get; private set; }
        public string LayerID
        {
            get
            {
                return Layer.ID;
            }
        }

        // label displayed in legend
        public abstract string Label { get; }

        // classification ID
        public abstract string ClassID { get; }

        // legend icon image
        public BitmapImage Icon
        {
            get
            {
                // get from cache
                lock (IconCache)
                {
                    if (IconCache.ContainsKey(IconKey))
                    {
                        return IconCache[IconKey];
                    }
                }

                BitmapImage bi = null;

                if (InRange)
                {
                    // request icon and save it to cache
                    // the bitmap image is instantiated immediately but the source is set async
                    bi = IconProvider(LayerID, ClassID, CurrentScale, IconSize);
                    if (bi != null)
                    {
                        lock (IconCache)
                        {
                            IconCache[IconKey] = bi;
                        }
                        NotifyOfPropertyChange(() => Icon);
                    }
                }

                return bi;
            }
        }

        // cache key
        public string IconKey
        {
            get
            {
                if (ClassID == null)
                {
                    return LayerID + "::" + MinScaleRange + "-" + MaxScaleRange;
                }
                return LayerID + "::" + ClassID + "::" + MinScaleRange + "-" + MaxScaleRange;
            }
        }


        // icon size (width or height)
        public int IconSize
        {
            get { return _IconSize; }
            set { _IconSize = value; }
        }
        private int _IconSize = Configuration.LegendIconSize;


        // visibility is tied to Map.Layer
        public bool IsVisible 
        {
            get
            {
                return Layer.Visible;
            }
            set
            {
                if (Layer.Visible != value)
                {
                    Layer.Visible = value;
                    NotifyOfPropertyChange(() => IsVisible);

                    // publish event
                    Events.Publish(new ChangeLayerVisibility
                    {
                        Layer = Layer
                    });
                }
            }
        }


        // is this item selected in the legend
        public bool IsSelected
        {
            get
            {
                return _IsSelected;
            }
            set
            {
                if (_IsSelected != value)
                {
                    _IsSelected = value;
                    NotifyOfPropertyChange(() => IsSelected);

                    // publish event
                    Events.Publish(new ChangeLayerSelection
                    {
                        Layer = Layer,
                        IsSelected = value
                    });
                }
            }
        }
        private bool _IsSelected;

       
        // scale and range    
        public abstract double MinScaleRange { get; }
        public abstract double MaxScaleRange { get; }
        public abstract double CurrentScale { get; set;}

        // is the item's scale constraint satisfied by the current range
        public bool InRange
        {
            get
            {
                return (CurrentScale >= MinScaleRange || MinScaleRange < 0) &&
                       (CurrentScale <= MaxScaleRange || MaxScaleRange < 0);
            }
        }

        // test for equality
        public bool Equals(LegendItemBase other)
        {
            return other.IconKey == IconKey;
        }


        // constructor
        protected LegendItemBase(Layer layer, 
            Func<string, string, double, int, BitmapImage> iconProvider, 
            IEventAggregator events)
        {
            Layer = layer;
            IconProvider = iconProvider;
            Events = events;
        }
    }

    // --------------------------------------------------------------------------------------------

    /// <summary>
    /// This is the view model representation of a layer in the legend
    /// A LegendItem may aggregate multiple LegendClassItem instances
    /// </summary>
    public class LegendItem : LegendItemBase
    {
        // the class id here is the first 'all' class in range
        public override string ClassID
        {
            get 
            {
                if (_ClassID == null)
                {
                    foreach (var c in Classes)
                    {
                        var cl = c.Classification;
                        if (!(cl.IsExact() || cl.IsRange()))
                        {
                            _ClassID = cl.ID;
                        }
                    }
                }
                return _ClassID;
            }
        }
        private string _ClassID;

        // use Layer.ID for the legend label in not in metadata
        public override string Label
        {
            get
            {
                return Layer.GetLayerName(LayerID).TruncateOnWordBoundary(LablelTruncLength);
            }
        }


        public string ToolTip
        {
            get
            {
                return Layer.GetLayerName(LayerID);
            }
        }


        // scale ranges
        public override double MinScaleRange
        {
            get
            {
                return Layer.MinScale;
            }
        }
        public override double MaxScaleRange
        {
            get
            {
                return Layer.MaxScale;
            }
        }
        public override double CurrentScale
        {
            get { return _CurrentScale; }
            set 
            { 
                _CurrentScale = value;
                NotifyOfPropertyChange(() => InRange);
                NotifyOfPropertyChange(() => IsSubclassExpanded);
                NotifyOfPropertyChange(() => IsLegendItemVisible);
                NotifyOfPropertyChange(() => IsIconVisible);
                NotifyOfPropertyChange(() => IsExpanderVisible);
                NotifyOfPropertyChange(() => IsOutOfScaleVisible);
                NotifyOfPropertyChange(() => IsDensityMapVisible);
                NotifyOfPropertyChange(() => Icon);

                // update all child items
                Classes.Apply((c)=> c.CurrentScale = value);
            }
        }
        private double _CurrentScale = -2;


        // The height of all classes together
        public double ClassesHeight
        {
            get
            {
                return (IconSize + 2) * Classes.Count;
            }
        }


        // child classification instances
        public BindableCollection<LegendClassItem> Classes
        {
            get
            {
                if (_Classes == null)
                {
                    _Classes = new BindableCollection<LegendClassItem>();
                    foreach (var cItem in Layer.Classifications)
                    {
                        var lci = new LegendClassItem(Layer, IconProvider, Events, cItem);
                        _Classes.Add(lci);
                    }
                }
                return _Classes;
            }
        }
        private BindableCollection<LegendClassItem> _Classes;


        // does this legend item have multiple classifications
        public bool IsSubClassed
        {
            get
            {
                return Classes.Count > 1;
            }
        }


        // visibilty states
        // this determines whether or not the legend item is shown in the legend
        // note: IsVisible is the visibility of the associated map layer
        public Visibility IsLegendItemVisible
        {
            get
            {
                return (!(Layer.HideLayersOutOfScale() && !InRange)) ? Visibility.Visible : Visibility.Collapsed;
            }
        }
        public Visibility IsIconVisible
        {
            get
            {
                return (!IsSubClassed && InRange) ? Visibility.Visible : Visibility.Collapsed;
            }
        }
        public Visibility IsExpanderVisible
        {
            get
            {
                return (IsSubClassed && InRange)  ? Visibility.Visible : Visibility.Collapsed;
            }
        }
        public Visibility IsOutOfScaleVisible
        {
            get
            {
                return (!InRange) ? Visibility.Visible : Visibility.Collapsed;
            }
        }


        public bool IsSubclassExpanded
        {
            get
            {
                return _IsSubclassExpanded && InRange;
            }
            set
            {
                _IsSubclassExpanded = value;
                NotifyOfPropertyChange(() => IsSubclassExpanded);
            }
        }
        private bool _IsSubclassExpanded;


        // optional filter list associated with this item
        public IList<FilterItem> Filters
        {
            get
            {
                if (_Filters == null)
                {
                    _Filters = FilterItem.ParseFilterItems(Layer);

                    // make sure the layer where clause is properly set when the filter is first loaded
                    if (FilterItem.ApplyFilterToLayer(_Filters, Layer))
                    {
                        // publish event
                        Events.Publish(new ChangeLayerFilter
                        {
                            Layer = Layer,
                            LegendItem = this
                        });
                    }
                }
                return _Filters;
            }
        }
        private IList<FilterItem> _Filters;


        // the string to display to the user re: filters in play
        public string FilterCaption
        {
            get
            {
                return FilterItem.CreateDisplayHeader(Filters).TruncateOnWordBoundary(36);
            }
        }


        // the string to display to the user re: filters in play
        public string FilterTooltip
        {
            get
            {
                return FilterItem.CreateDisplayHeader(Filters);
            }
        }


        // do we have any filters
        public bool HasAnyFilters
        {
            get
            {
                return Filters.Count > 0;
            }
        }


        // do we have any enabled filters
        public bool HasActiveFilters
        {
            get
            {
                return Filters.Any(f => f.IsEnabled);
            }
        }


        // call this when the filter changes
        public void FilterChange()
        {
            NotifyOfPropertyChange(() => FilterCaption);
            NotifyOfPropertyChange(() => FilterTooltip);
            NotifyOfPropertyChange(() => HasActiveFilters);

            // apply the filter to the layer
            // if there was an actual change
            if (FilterItem.ApplyFilterToLayer(Filters, Layer))
            {
                // publish event
                Events.Publish(new ChangeLayerFilter
                {
                    Layer = Layer,
                    LegendItem = this
                });
            }
        }


        // density mapping metadata
        public DensityMapItem DensityMapItem
        {
            get
            {
                if (_DensityMapItem == null)
                {
                    _DensityMapItem = DensityMapItem.ParseDensityMapItem(Layer);

                    // call this to make sure density map layer is initialized on
                    if (_DensityMapItem.IsVisible)
                    {
                        DensityMapVisibilityChange(true);
                    }
                }
                return _DensityMapItem;
            }
        }
        private DensityMapItem _DensityMapItem;


        // support density mapping
        public bool HasDensityMapSupport
        {
            get
            {
                return DensityMapItem.IsSupported;
            }
        }


        // is it visible
        public bool IsDensityMapVisible
        {
            get
            {
                return DensityMapItem.IsVisible && InRange;
            }
            set
            {
                bool curVal = DensityMapItem.IsVisible;
                if (curVal != value)
                {
                    DensityMapItem.IsVisible = value;
                    DensityMapVisibilityChange(value);
                }
            }
        }


        // call when there is a change in the visibility state of the density map layer
        public void DensityMapVisibilityChange(bool val)
        {
            NotifyOfPropertyChange(() => IsDensityMapVisible);

            // update layer metadata so visibility is in sync
            Layer.SetDensityMappingString(DensityMapItem.SerialzeDensityMapItem(DensityMapItem));

            // publish event
            Events.Publish(new ChangeLayerDensityMapEnabled
            {
                IsEnabled = val,
                Layer = Layer
            });
        }


        // constructor
        public LegendItem(Layer layer, 
            Func<string, string, double, int, BitmapImage> iconProvider,
            IEventAggregator events) : 
            base(layer, iconProvider, events)
        {
        }
    }


    // --------------------------------------------------------------------------------------------

    /// <summary>
    /// This is the view model representation of a layer classification in the legend
    /// </summary>
    public class LegendClassItem : LegendItemBase
    {
        public Classification Classification { get; private set; }

        public override string ClassID
        {
            get
            {
                return Classification.ID;
            }
        }

        // label displayed in legend use layer ID
        public override string Label
        {
            get
            {
                int cID;
                if (Int32.TryParse(ClassID, out cID))
                {
                    // this is merely a number, so we need to build our own label
                    if (Classification.IsExact())
                    {
                        return Classification.ExactValue;
                    }
                    if (Classification.IsRange())
                    {
                        return Classification.MinRange + " - " + Classification.MaxRange;
                    }
                }
                return ClassID;
            }
        }

        // scale ranges
        public override double MinScaleRange
        {
            get
            {
                return Classification.MinScale;
            }
        }
        public override double MaxScaleRange
        {
            get
            {
                return Classification.MaxScale;
            }
        }
        public override double CurrentScale
        {
            get { return _CurrentScale; }
            set
            {
                _CurrentScale = value;
                NotifyOfPropertyChange(() => Icon);
            }
        }
        private double _CurrentScale = -2;


        // constructor
        public LegendClassItem(Layer layer, 
            Func<string, string, double, int, BitmapImage> iconProvider,
            IEventAggregator events,
            Classification classification)
            : base(layer, iconProvider, events)
        {
            Classification = classification;
        }
    }

}
